package sim.test;

import camera.AbstractCamera;
import sim.camera.SimCam;
import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.collision.CollisionResults;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Line;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Random;
import matchingAlgorithms.AbstractMatching;
import matchingAlgorithms.HungarianMatching;
import sim.dynamics.RandomizedModelMotion;
import sim.system.SystemSetup;
import sim.env.Environment;
import sim.env.Obstacle;
import sim.env.ObstacleGeometry;
import sim.models.BaseModel;
import sim.models.GolemModel;
import sim.models.ModelFactory;
import sim.models.SinbadModel;

/**
 * Test multiple viewports.
 * 
 * @author Ahmed Abdelkader
 */
public class Simulator2 extends SimpleApplication {

    private final Vector3f X = Vector3f.UNIT_X;
    private final Vector3f Y = Vector3f.UNIT_Y;
    private final Vector3f Z = Vector3f.UNIT_Z;
    private BulletAppState bulletAppState;
    private ArrayList<BaseModel> models = new ArrayList<BaseModel>();
    private ArrayList<AbstractCamera> cameras = new ArrayList<AbstractCamera>();
    private Node bodies;
    private final float cameraHeight = 160f;
    private final int REFRESH_RATE = 500;
    private Environment environment;
    private SystemSetup sys;
    private int width, height, subjects;
    private ArrayList<Obstacle> obstacles = new ArrayList<Obstacle>();
    private Vector3f inModels[];
    Random rand = new Random();
    private AbstractMatching matchingAlgo = new HungarianMatching();
    private ActionListener actionListener = new ActionListener() {

        public void onAction(String name, boolean pressed, float tpf) {
            System.out.println(renderManager.removeMainView("Top Left"));
        }
    };

    private PhysicsSpace getPhysicsSpace() {
        return bulletAppState.getPhysicsSpace();
    }

    private void init() {
        try {
            // Open the file that is the first 
            // command line parameter
            FileInputStream fstream = new FileInputStream("setup.txt");
            // Get the object of DataInputStream
            DataInputStream in = new DataInputStream(fstream);
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String strLine = br.readLine(); // the first line the map width and height
            String[] tokens = strLine.split(" ");


            strLine = br.readLine(); // the first line the map width and height
            tokens = strLine.split(" ");


            strLine = br.readLine(); // the third line number of moving subjects
            tokens = strLine.split(" ");
            int numPoints = Integer.parseInt(tokens[0]);
            int[] xS = new int[numPoints];
            int[] yS = new int[numPoints];
            strLine = br.readLine(); // the third line number of corners in the circumference
            tokens = strLine.split(" ");
            // get the x points
            for (int i = 0; i < tokens.length; i++) {
                xS[i] = Integer.parseInt(tokens[i]);
            }
            strLine = br.readLine(); // the third line number of moving subjects
            strLine = br.readLine(); // the sixth line number of moving subjects
            tokens = strLine.split(" ");
            int numberOfModels = Integer.parseInt(tokens[0]);
            inModels = new Vector3f[numberOfModels];
            for (int i = 0; i < numberOfModels; i++) {
                strLine = br.readLine(); // read model initial position.
                tokens = strLine.split(" ");
                inModels[i] = new Vector3f(Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1]), Integer.parseInt(tokens[2]));
                models.add(sys.addModel(new SinbadModel(this)));
                models.get(i).setLocalTranslation(inModels[i].x, inModels[i].y, inModels[i].z);
            }

            strLine = br.readLine(); // Read in number of obstacles.
            tokens = strLine.split(" ");
            int numberOfobstacles = Integer.parseInt(tokens[0]);
            for (int i = 1; i <= numberOfobstacles; i++) {
                strLine = br.readLine(); // read obstacle properties
                tokens = strLine.split(" ");
                environment.createObstacle(Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1]), Integer.parseInt(tokens[2]), tokens[3]);
                environment.getObstacles().get(i).setLocalTranslation(Integer.parseInt(tokens[4]), Integer.parseInt(tokens[5]), Integer.parseInt(tokens[6]));

            }
            obstacles = environment.getObstacles();

            strLine = br.readLine(); // Read in master camera videport
            tokens = strLine.split(" ");

            cam.setLocation(new Vector3f(0f, 1800f, 0f));
            cam.lookAt(Y.negate(), Z);
            cam.setFrustumFar(2400f);
            renderManager.createMainView("main", cam.clone());


            float masterPortValues[] = {Float.parseFloat(tokens[0]), Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2]), Float.parseFloat(tokens[3])};
            cam.setViewPort(masterPortValues[0], masterPortValues[1], masterPortValues[2], masterPortValues[3]);

            strLine = br.readLine(); // Read in number of cameras
            tokens = strLine.split(" ");
            int numberOfCameras = Integer.parseInt(tokens[0]);
            int r = 400;
            for (int i = 0; i < numberOfCameras; i++) {
                strLine = br.readLine(); // Read slave camera properties
                tokens = strLine.split(" ");
                AbstractCamera slaveCam = new SimCam(cam.clone());
                float viewPortValues[] = {Float.parseFloat(tokens[0]), Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2]), Float.parseFloat(tokens[3])};
                Vector3f location = new Vector3f(Float.parseFloat(tokens[4]), Float.parseFloat(tokens[5]), Float.parseFloat(tokens[6]));
                float angle = Float.parseFloat(tokens[7]);
                float dirX = FastMath.abs(r * FastMath.cos((float) Math.toRadians(angle)));
                float dirY = FastMath.abs(r * FastMath.sin((float) Math.toRadians(angle)));

                if (Float.compare(dirX, dirY) >= 0) {
                    dirY = dirY / dirX;
                    dirX = dirX / dirX;
                } else {
                    dirX = dirX / dirY;
                    dirY = dirY / dirY;
                }
                if (Float.compare(angle, 90) <= 0) {
                    if (Float.compare(dirX, 0) >= 0) {
                        dirX = -dirX;
                    }
                    if (Float.compare(dirY, 0) <= 0) {
                        dirY = -dirY;
                    }
                } else if (Float.compare(angle, 180) <= 0) {
                    if (Float.compare(dirX, 0) <= 0) {
                        dirX = -dirX;
                    }
                    if (Float.compare(dirY, 0) <= 0) {
                        dirY = -dirY;
                    }
                } else if (Float.compare(angle, 270) <= 0) {
                    if (Float.compare(dirX, 0) <= 0) {
                        dirX = -dirX;
                    }
                    if (Float.compare(dirY, 0) >= 0) {
                        dirY = -dirY;
                    }
                } else {
                    if (Float.compare(dirX, 0) >= 0) {
                        dirX = -dirX;
                    }
                    if (Float.compare(dirY, 0) >= 0) {
                        dirY = -dirY;
                    }
                }
                Vector3f dir = new Vector3f(dirX, 0, dirY);
                slaveCam.setViewPort(viewPortValues);
                sys.AddCamera(slaveCam, location, dir, tokens[8], renderManager, true, Boolean.parseBoolean(tokens[9]));
            }
            long seed = Long.parseLong(br.readLine());
            RandomizedModelMotion.rand.setSeed(seed);
            in.close();
        } catch (Exception e) {//Catch exception if any
            System.err.println("Error: " + e.getMessage());
        }

    }

    @Override
    public void simpleInitApp() {
        bulletAppState = new BulletAppState();
        bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
        stateManager.attach(bulletAppState);
        width = 1200;
        height = 1200;
        subjects = 6;
        bodies = new Node("Bodies");

        environment = new Environment(getPhysicsSpace(), bodies, this, width, height);
        sys = new SystemSetup(getPhysicsSpace(), cameras, rootNode, bodies, width, height);

        sys.AddDirectLight(ColorRGBA.White, Y);
        sys.AddDirectLight(ColorRGBA.White, Y.negate());
        init();
        /* Setup models */
        Node walls = new Node("Obstacles");
        for (Obstacle wall : obstacles) {
            walls.attachChild(wall.getSpatial());
        }
        rootNode.attachChild(walls);

        /* Setup input mapping */
        inputManager.addMapping("ToggleLights", new KeyTrigger(KeyInput.KEY_F));
        inputManager.addListener(actionListener, "ToggleLights");
        viewPort.setBackgroundColor(ColorRGBA.Black);

    }
    int ccc = 0;
    int[] matching;
    Geometry[] vis = new Geometry[40];

    @Override
    public void simpleUpdate(float tpf) {
        for (BaseModel model : models) {
            model.move();
        }
//
//        bulletAppState.update(tpf);
//
//        // Camera update (PTZ) or re-match
//        if (ccc++ % REFRESH_RATE > 0) {
//            for (int i = 0; i < cameras.size(); i++) {
//                if (matching[i] == -1) {
//                    continue;
//                }
//                Vector3f modelPos = new Vector3f(models.get(matching[i]).getLocalTranslation());
//                modelPos.setY(cameras.get(i).getLocation().getY());
//                Vector3f dir = modelPos.subtract(cameras.get(i).getLocation());
//                dir.setY(cameras.get(i).getLocation().getY());
//                cameras.get(i).getCam().lookAt(dir, Y);
//                if (vis[i] != null) {
//                    vis[i].removeFromParent();
//                }
//                vis[i] = makeLine("" + i, modelPos, cameras.get(i).getLocation(),
//                        pick(cameras.get(i), models.get(matching[i])) ? ColorRGBA.Red : ColorRGBA.Blue);
//                rootNode.attachChild(vis[i]);
//            }
//        } else {
//            matching = matchingAlgo.match(buildGraph());
//        }
    }

    public float[][] buildGraph() {
        boolean[][] picks = new boolean[cameras.size()][models.size()];
        float[][] g = new float[cameras.size()][models.size()];
        for (int i = 0; i < cameras.size(); i++) {
            for (int j = 0; j < models.size(); j++) {
                g[i][j] = (picks[i][j] = pick(cameras.get(i), models.get(j)))
                        ? cameras.get(i).getLocation().distance(models.get(j).getLocalTranslation()) : (1 << 30);
            }
        }
        return g;
    }

    public boolean pick(AbstractCamera cam, BaseModel model) {
        Ray ray = new Ray(cam.getLocation(), model.getLocalTranslation().subtract(cam.getLocation()));
        CollisionResults results = new CollisionResults();
        bodies.collideWith(ray, results);
        return results.size() > 0 && model.contains(results.getCollision(0).getGeometry());
    }

    public Geometry makeLine(String name, Vector3f s, Vector3f e, ColorRGBA c) {
        Line line = new Line(s, e);
        Geometry geom = new Geometry(name, line);
        Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat1.setColor("m_Color", c);
        geom.setMaterial(mat1);
        return geom;
    }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }

    public static void main(String[] args) {
        Simulator2 app = new Simulator2();
        app.start();
    }
}
